<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Spring面经 | 无人深空</title><meta name="keywords" content="Spring"><meta name="author" content="zcm"><meta name="copyright" content="zcm"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="Spring 面经1.Spring、SpringMVC、SpringBoot、SpringCloud 的区别与联系 Spring 框架如下图     四者简介  Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架  能够编写更干净、更可管理、并且更易于测试的代码   Spring MVC 是 Spring 的一个模块，一个 web 框架  通过 Dispatcher Se">
<meta property="og:type" content="article">
<meta property="og:title" content="Spring面经">
<meta property="og:url" content="https://gitee.com/zcmmmm/zcmmmm/2022/10/23/Spring%E9%9D%A2%E7%BB%8F/index.html">
<meta property="og:site_name" content="无人深空">
<meta property="og:description" content="Spring 面经1.Spring、SpringMVC、SpringBoot、SpringCloud 的区别与联系 Spring 框架如下图     四者简介  Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架  能够编写更干净、更可管理、并且更易于测试的代码   Spring MVC 是 Spring 的一个模块，一个 web 框架  通过 Dispatcher Se">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg">
<meta property="article:published_time" content="2022-10-23T02:22:30.000Z">
<meta property="article:modified_time" content="2023-05-02T02:09:28.994Z">
<meta property="article:author" content="zcm">
<meta property="article:tag" content="Spring">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://gitee.com/zcmmmm/zcmmmm/2022/10/23/Spring%E9%9D%A2%E7%BB%8F/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Spring面经',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-05-02 10:09:28'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/categories.css?v1"><link rel="stylesheet" href="/css/navigation.css?v1"><link rel="stylesheet" href="/css/custom.css?v1"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://s2.loli.net/2022/09/22/36LN4kcjYOd9HT8.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">43</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">25</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">11</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">无人深空</a></span><div id="menus"></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">Spring面经</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-10-23T02:22:30.000Z" title="发表于 2022-10-23 10:22:30">2022-10-23</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-05-02T02:09:28.994Z" title="更新于 2023-05-02 10:09:28">2023-05-02</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E9%9D%A2%E7%BB%8F/">面经</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Spring/">Spring</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">7.5k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>24分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Spring面经"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="Spring-面经"><a href="#Spring-面经" class="headerlink" title="Spring 面经"></a>Spring 面经</h2><h3 id="1-Spring、SpringMVC、SpringBoot、SpringCloud-的区别与联系"><a href="#1-Spring、SpringMVC、SpringBoot、SpringCloud-的区别与联系" class="headerlink" title="1.Spring、SpringMVC、SpringBoot、SpringCloud 的区别与联系"></a>1.Spring、SpringMVC、SpringBoot、SpringCloud 的区别与联系</h3><blockquote>
<p>Spring 框架如下图</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring%E6%A1%86%E6%9E%B6.jpg" alt="Spring框架"></p>
<ol>
<li><p>四者简介</p>
<ul>
<li><p><strong>Spring 是一个轻量级的<code>控制反转(IoC)</code>和<code>面向切面(AOP)</code>的容器框架</strong></p>
<blockquote>
<p>能够编写更干净、更可管理、并且更易于测试的代码</p>
</blockquote>
</li>
<li><p><strong>Spring MVC 是 Spring 的一个模块，一个 web 框架</strong></p>
<blockquote>
<p>通过 <code>Dispatcher Servlet、ModelAndView、View Resolver</code>，开发 web 应用变得很容易</p>
<p>主要针对网站应用程序或者服务开发——URL 路由、Session、模板引擎、静态 Web 资源等等</p>
</blockquote>
</li>
<li><p><strong>Spring boot 简化了 spring 的配置流程</strong></p>
<blockquote>
<p>Spring 配置复杂，繁琐，所以推出了 Spring boot</p>
<p><strong>约定优于配置</strong></p>
</blockquote>
</li>
<li><p><strong>Spring Cloud 构建于 Spring Boot 之上</strong></p>
<blockquote>
<p>是一个关注全局的服务治理框架</p>
</blockquote>
</li>
</ul>
</li>
<li><p>Spring 和 SpringMVC</p>
<blockquote>
<p>Spring 是一个一站式的轻量级的 java 开发框架，核心是控制反转（IOC）和面向切面（AOP），针对于开发的 WEB 层（SpringMVC）、业务层（IOC）、持久层（jdbcTemplate）等都提供了多种配置解决方案</p>
<p>SpringMVC 是 Spring 基础之上的一个 MVC 框架，主要处理 web 开发的路径映射和视图渲染，属于Spring 框架中 WEB 层开发的一部分</p>
</blockquote>
</li>
<li><p>SpringMVC 和 SpringBoot</p>
<blockquote>
<p>SpringMVC 属于一个企业 WEB 开发的 MVC 框架，涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等，XML、config 等配置相对比较繁琐复杂</p>
<p>SpringBoot 框架相对于 SpringMVC 框架来说，更专注于开发微服务后台接口，不开发前端视图</p>
</blockquote>
</li>
<li><p>SpringBoot 和 SpringCloud</p>
</li>
</ol>
<blockquote>
<p>SpringBoot 使用了<strong>默认大于配置</strong>的理念，集成了快速开发的 Spring 多个插件，同时自动过滤不需要配置的多余的插件，简化了项目的开发配置流程，一定程度上取消 xml 配置，是一套快速配置开发的脚手架，能快速开发单个微服务</p>
<p>SpringCloud 大部分的功能插件都是基于 SpringBoot 去实现的，SpringCloud 关注于全局的微服务整合和管理，将多个 SpringBoot 单体微服务进行整合以及管理</p>
<p>SpringCloud 依赖于 SpringBoot 开发，而 SpringBoot 可以独立开发</p>
</blockquote>
<ol start="5">
<li><p>四者总结</p>
<ul>
<li>Spring 是核心，提供了基础功能</li>
<li>Spring MVC 是基于 Spring 的一个 MVC 框架 </li>
<li>Spring Boot 是为简化 Spring 配置的快速开发整合包</li>
<li>Spring Cloud 是构建在 Spring Boot 之上的服务治理框架</li>
</ul>
</li>
</ol>
<h3 id="2-Spring-框架中的-IOC"><a href="#2-Spring-框架中的-IOC" class="headerlink" title="2.Spring 框架中的 IOC"></a>2.Spring 框架中的 IOC</h3><blockquote>
<p>Spring 中的 <code>org.springframework.beans</code> 包和 <code>org.springframework.context</code> 包构成了 Spring 框架 IoC 容器的基础</p>
<p><code>BeanFactory</code> 接口提供了一个先进的配置机制，使得任何类型的对象的配置成为可能</p>
<p><code>ApplicationContex</code> 接口对 <code>BeanFactory</code>（是一个子接口）进行了扩展，在 <code>BeanFactory</code> 的基础上添加了其他功能，比如与 Spring 的 AOP 更容易集成，也提供了处理 <code>message resource</code> 的机制（用于国际化）、事件传播以及应用层的特别配置，比如针对 Web 应用的 <code>WebApplicationContext</code></p>
<p><code>org.springframework.beans.factory.BeanFactory</code> 是 Spring IoC 容器的具体实现，用来包装和管理前面提到的各种 bean</p>
<p>BeanFactory 接口是 Spring IoC 容器的核心接口</p>
<p><strong>IOC：把对象的创建、初始化、销毁交给 Spring 来管理，而不是由开发者控制，实现控制反转</strong></p>
</blockquote>
<h3 id="3-依赖注入-DI"><a href="#3-依赖注入-DI" class="headerlink" title="3.依赖注入(DI)"></a>3.依赖注入(DI)</h3><ol>
<li><p>定义</p>
<blockquote>
<p>依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下，将其他对象所依赖的功能对象实例化的模式</p>
<p>这就需要一种机制用来激活相应的组件以提供特定的功能，所以<strong>依赖注入是控制反转的基础</strong></p>
<p>否则如果在组件不受框架控制的情况下，框架又怎么知道要创建哪个组件</p>
</blockquote>
</li>
<li><p>实现方式</p>
<blockquote>
<p>构造器注入和 Setter 方法注入</p>
<p><strong>最好的解决方案是用构造器参数实现强制依赖，setter 方法实现可选依赖</strong></p>
</blockquote>
<ul>
<li>构造器注入</li>
<li>Setter 方法注入</li>
<li>接口注入</li>
</ul>
</li>
</ol>
<h3 id="4-BeanFactory-和-ApplicationContext-有什么区别"><a href="#4-BeanFactory-和-ApplicationContext-有什么区别" class="headerlink" title="4.BeanFactory 和 ApplicationContext 有什么区别"></a>4.BeanFactory 和 ApplicationContext 有什么区别</h3><ol>
<li><p>BeanFactory</p>
<blockquote>
<p>BeanFactory 可以理解为含有 bean 集合的工厂类</p>
<p>BeanFactory 包含了种 bean 的定义，以便在接收到客户端请求时将对应的 bean 实例化</p>
<p>BeanFactory 还能在实例化对象的时生成协作类之间的关系</p>
<p>此举将 bean 自身与 bean 客户端的配置中解放出来</p>
<p>BeanFactory 还包含 了 bean 生命周期的控制，调用客户端的初始化方法（initialization methods）和销毁方法（destruction methods）</p>
</blockquote>
</li>
<li><p>ApplicationContext</p>
<blockquote>
<p>以下是几种较常见的 ApplicationContext 实现方式：</p>
</blockquote>
<ul>
<li><p><code>ClassPathXmlApplicationContext</code>：从 classpath 的 XML 配置文件中读取上下文，并生成上下文定义。应用程序上下文从程序环境变量中</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">ApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassPathXmlApplicationContext</span>(“bean.xml”); </span><br></pre></td></tr></table></figure>
</li>
<li><p><code>FileSystemXmlApplicationContext</code>：由文件系统中的 XML 配置文件读取上下文</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="type">ApplicationContext</span> <span class="variable">context</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileSystemXmlApplicationContext</span>(“bean.xml”); </span><br></pre></td></tr></table></figure>
</li>
<li><p><code>XmlWebApplicationContext</code>：由 Web 应用的 XML 文件读取上下文</p>
</li>
<li><p><code>AnnotationConfigApplicationContext</code>（基于 Java 配置启动容器）</p>
</li>
</ul>
</li>
<li><p>两者对比</p>
<blockquote>
<p>从表面上看，<code>ApplicationContext</code> 如同 <code>BeanFactory</code> 一样具有 bean 定义、bean 关联关系的设置，根据请求分发 bean 的功能</p>
<p>但 <code>ApplicationContext</code>在此基础上还提供了其他的功能</p>
</blockquote>
<ul>
<li>提供了支持国际化的文本消息</li>
<li>统一的资源文件读取方式</li>
<li>已在监听器中注册的 bean 的事件</li>
</ul>
</li>
</ol>
<h3 id="5-Spring-bean"><a href="#5-Spring-bean" class="headerlink" title="5.Spring bean"></a>5.Spring bean</h3><ol>
<li><p>Spring bean 的生命周期</p>
<ul>
<li>Spring 容器从 XML 文件中读取 bean 的定义，并实例化 bean</li>
<li>Spring 根据 bean 的定义填充所有的属性</li>
<li>如果 bean 实现了 BeanNameAware 接口，Spring 传递 bean 的 ID 到 setBeanName 方法</li>
<li>如果 Bean 实现了 BeanFactoryAware 接口，Spring 传递 beanfactory 给 setBeanFactory 方法</li>
<li>如果有任何与 bean 相关联的 <code>BeanPostProcessors</code>，Spring 会在 <code>ostProcesserBeforeInitialization()</code> 方法内调用它们</li>
<li>如果 bean 实现 <code>IntializingBean</code> 了，调用它的 <code>afterPropertySet</code> 方法，如果 bean 声明了初始化方法，调用此初始化方法</li>
<li>如果有 <code>BeanPostProcessors</code> 和 bean 关联，这些 bean 的 <code>postProcessAfterInitialization()</code> 方法将被调用</li>
<li>如果 bean 实现了 <code>DisposableBean</code>，它将调用 <code>destroy()</code> 方法</li>
</ul>
</li>
<li><p>哪些是重要的 bean 生命周期方法（可以重载吗）</p>
<blockquote>
<p>有两个重要的 bean 生命周期方法</p>
<p>第一个是 <code>setup</code>，它是在容器加载 bean 的时候被调用</p>
<p>第二个方法是 <code>teardown</code> 它是在容器卸载类的时候被调用</p>
<p>Thebean 标签有两个重要的属性（<code>init-method</code> 和 <code>destroy-method</code>）</p>
<p>用它们可以自己定制初始化和注销方法</p>
<p>它们也有相应的注解（<code>@PostConstruct</code> 和 <code>@PreDestroy</code>）</p>
</blockquote>
</li>
<li><p>Spring Bean 的作用域之间有什么区别</p>
<ul>
<li><p><code>singleton</code></p>
<blockquote>
<p>这种 bean 范围是默认的，这种范围确保不管接受到多少个请求，<strong>每个容器中只有一个bean 的实例</strong>，单例的模式由 bean factory 自身来维护</p>
</blockquote>
</li>
<li><p><code>prototype</code></p>
<blockquote>
<p><strong>原形范围与单例范围相反，为每一个 bean 请求提供一个实例</strong></p>
</blockquote>
</li>
<li><p><code>request</code></p>
<blockquote>
<p>在请求 bean 范围内会每一个来自客户端的网络请求创建一个实例</p>
<p>在请求完成以后，bean 会失效并被垃圾回收器回收</p>
</blockquote>
</li>
<li><p><code>Session</code></p>
<blockquote>
<p>与请求范围类似，确保每个 session 中有一个 bean 的实例</p>
<p>session 过期后，bean 会随之失效</p>
</blockquote>
</li>
<li><p><code>global-session</code></p>
<blockquote>
<p>global-session 和 Portlet 应用相关</p>
<p>当你的应用部署在 Portlet 容器中工作时，它包含很多 Portlet </p>
<p>如果想要声明让所有的 Portlet 共用全局的存储变量的话，那么这全局变量需要存储在 <code>global-session</code> 中</p>
<p>全局作用域与 Servlet 中的 session 作用域效果相同</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h3 id="6-Spring-初始化-bean-的流程"><a href="#6-Spring-初始化-bean-的流程" class="headerlink" title="6.Spring 初始化 bean 的流程"></a>6.Spring 初始化 bean 的流程</h3><blockquote>
<p>Spring Bean 的初始化主要实现在 <code>bean.factory</code> 包下的<code>AbstractAutowireCapableBeanFactory</code>类中<br>具体实现流程为这个类里的三个初始化流程，分别为</p>
</blockquote>
<ul>
<li><p>实例化 <code>createBeanInstance</code></p>
<blockquote>
<p>简单理解就是new了一个对象<br>里面包含了推断构造方法，简单来说就是对 bean 进行实例化</p>
</blockquote>
</li>
<li><p>属性赋值（注入）  <code>populateBean</code> </p>
<blockquote>
<p>为实例化中 new 出来的对象填充属性<br>处理 @AutoWried，调用 bean 的实例化后的方法</p>
</blockquote>
</li>
<li><p>Bean 的初始化 <code>initializeBean</code></p>
<blockquote>
<p>执行 aware 接口中的方法，初始化方法，完成 AOP 代理<br>调用 bean 的初始化方法， 同时 <code>initializeBean</code>前后还会对 <code>aware</code>进行调用处理<br>BeanNameAware、BeanClassLoaderAware、BeanFactoryAware、EnvironmentAware、ApplicationContextAware（BeanPostProcessor before）<br>之后调用 <code>invokeInitMethods</code> 就是 <code>initializeBean</code>的流程，之后会调用一次 <code>BeanPostProcessor</code>的另一个调用点 after</p>
</blockquote>
</li>
<li><p>DisposableBean 生命周期的销毁</p>
<blockquote>
<p><code>ConfigurableApplicationContext</code> 的 <code>close()</code>方法作为入口，实现是通过循环取所有实现了DisposableBean 接口的 Bean 然后调用其 destroy() 方法</p>
</blockquote>
</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/bean%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B.jpg" alt="bean初始化流程"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/bean%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B2.png" alt="bean初始化流程2"></p>
<h3 id="7-Spring-的循环依赖"><a href="#7-Spring-的循环依赖" class="headerlink" title="7.Spring 的循环依赖"></a>7.<a target="_blank" rel="noopener" href="https://www.cnblogs.com/daimzh/p/13256413.html">Spring 的循环依赖</a></h3><ol>
<li><p>定义</p>
<blockquote>
<p>A 与 B 相互依赖</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96.png" alt="循环依赖"></p>
<ul>
<li><p>举例1：A 和 B 相互依赖</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="comment">// A中注入了B</span></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">	<span class="keyword">private</span> B b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">    <span class="comment">// B中也注入了A</span></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">	<span class="keyword">private</span> A a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>举例2：特殊情况——A 依赖于 A</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="comment">// A中注入了A</span></span><br><span class="line">	<span class="meta">@Autowired</span></span><br><span class="line">	<span class="keyword">private</span> A a;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p>什么情况下循环依赖可以被处理</p>
<blockquote>
<p>Spring 解决循环依赖是有前置条件的</p>
</blockquote>
<ul>
<li>出现循环依赖的 Bean 必须要是单例</li>
<li>依赖注入的方式<strong>不能全是构造器注入</strong>的方式</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// A 与 B 的相互依赖都是通过构造器注入的方式，这个时候循环依赖是无法被解决</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">	<span class="keyword">public</span> <span class="title function_">A</span><span class="params">(B b)</span> &#123;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</span> &#123;</span><br><span class="line">	<span class="keyword">public</span> <span class="title function_">B</span><span class="params">(A a)</span>&#123;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>循环依赖的解决情况跟注入方式的关系如下</p>
</blockquote>
<table>
<thead>
<tr>
<th><strong>依赖情况</strong></th>
<th><strong>依赖注入方式</strong></th>
<th><strong>循环依赖是否被解决</strong></th>
</tr>
</thead>
<tbody><tr>
<td>AB相互依赖（循环依赖）</td>
<td>均采用setter方法注入</td>
<td>是</td>
</tr>
<tr>
<td>AB相互依赖（循环依赖）</td>
<td>均采用构造器注入</td>
<td>否</td>
</tr>
<tr>
<td>AB相互依赖（循环依赖）</td>
<td>A中注入B的方式为setter方法，B中注入A的方式为构造器</td>
<td>是</td>
</tr>
<tr>
<td>AB相互依赖（循环依赖）</td>
<td>B中注入A的方式为setter方法，A中注入B的方式为构造器</td>
<td>否</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>为什么在上表中的第三种情况的循环依赖能被解决，而第四种情况不能被解决呢</strong></p>
<p><strong>提示：Spring 在创建 Bean 时默认会根据自然排序进行创建，所以 A 会先于 B 进行创建</strong></p>
</blockquote>
</li>
<li><p>Spring 是如何解决循环依赖的</p>
<blockquote>
<p>关于循环依赖的解决方式应该要分两种情况来讨论</p>
</blockquote>
<ul>
<li><p>简单的循环依赖（没有AOP）</p>
<ul>
<li><p><strong>调用 <code>getSingleton(beanName)</code></strong></p>
<blockquote>
<p>这个方法又会调用 <code>getSingleton(beanName, true)</code></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">getSingleton</span><span class="params">(String beanName)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> getSingleton(beanName, <span class="literal">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p><code>getSingleton(beanName, true)</code>这个方法实际上就是到缓存中尝试去获取 Bean，整个缓存分为三级</p>
</blockquote>
<ul>
<li><code>singletonObjects</code>一级缓存，存储的是所有创建好了的单例Bean</li>
<li><code>earlySingletonObjects</code>完成实例化，但是还未进行属性注入及初始化的对象</li>
<li><code>singletonFactories</code>提前暴露的一个单例工厂，二级缓存中存储的就是从这个工厂中获取到的对象</li>
</ul>
<blockquote>
<p>因为A是第一次被创建，所以不管哪个缓存中必然都是没有的，因此会进入 getSingleton 的另外一个重载方法 <code>getSingleton(beanName, singletonFactory)</code></p>
</blockquote>
</li>
<li><p><strong>调用 <code>getSingleton(beanName, singletonFactory)</code></strong></p>
<blockquote>
<p>通过 <code>createBean</code> 方法返回的 Bean 最终被放到了一级缓存，也就是单例池中</p>
<p>可以得出一个结论：<strong>一级缓存中存储的是已经完全创建好了的单例 Bean</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">getSingleton</span><span class="params">(String beanName, ObjectFactory&lt;?&gt; singletonFactory)</span> &#123;</span><br><span class="line">    Assert.notNull(beanName, <span class="string">&quot;Bean name must not be null&quot;</span>);</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>.singletonObjects) &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">singletonObject</span> <span class="operator">=</span> <span class="built_in">this</span>.singletonObjects.get(beanName);</span><br><span class="line">        <span class="keyword">if</span> (singletonObject == <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// ....</span></span><br><span class="line">            <span class="comment">// 省略异常处理及日志</span></span><br><span class="line">            <span class="comment">// ....</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">// 在单例对象创建前先做一个标记</span></span><br><span class="line">            <span class="comment">// 将beanName放入到singletonsCurrentlyInCreation这个集合中</span></span><br><span class="line">            <span class="comment">// 标志着这个单例Bean正在创建</span></span><br><span class="line">            <span class="comment">// 如果同一个单例Bean多次被创建，这里会抛出异常</span></span><br><span class="line">            beforeSingletonCreation(beanName);</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">newSingleton</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">recordSuppressedExceptions</span> <span class="operator">=</span> (<span class="built_in">this</span>.suppressedExceptions == <span class="literal">null</span>);</span><br><span class="line">            <span class="keyword">if</span> (recordSuppressedExceptions) &#123;</span><br><span class="line">                <span class="built_in">this</span>.suppressedExceptions = <span class="keyword">new</span> <span class="title class_">LinkedHashSet</span>&lt;&gt;();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 上游传入的lambda在这里会被执行，调用createBean方法创建一个Bean后返回</span></span><br><span class="line">                singletonObject = singletonFactory.getObject();</span><br><span class="line">                newSingleton = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">            <span class="comment">// 省略catch异常处理</span></span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">            <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (recordSuppressedExceptions) &#123;</span><br><span class="line">                    <span class="built_in">this</span>.suppressedExceptions = <span class="literal">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除</span></span><br><span class="line">                afterSingletonCreation(beanName);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (newSingleton) &#123;</span><br><span class="line">                <span class="comment">// 添加到一级缓存singletonObjects中</span></span><br><span class="line">                addSingleton(beanName, singletonObject);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> singletonObject;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>调用 <code>addSingletonFactory</code>方法</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/addSingletonFactory.png" alt="addSingletonFactory"></p>
<blockquote>
<p>在完成 Bean 的实例化后，属性注入之前 Spring 将 Bean 包装成一个工厂添加进了三级缓存中，对应源码如下：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 这里传入的参数也是一个lambda表达式，() -&gt; getEarlyBeanReference(beanName, mbd, bean)</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">addSingletonFactory</span><span class="params">(String beanName, ObjectFactory&lt;?&gt; singletonFactory)</span> &#123;</span><br><span class="line">    Assert.notNull(singletonFactory, <span class="string">&quot;Singleton factory must not be null&quot;</span>);</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>.singletonObjects) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">this</span>.singletonObjects.containsKey(beanName)) &#123;</span><br><span class="line">            <span class="comment">// 添加到三级缓存中</span></span><br><span class="line">            <span class="built_in">this</span>.singletonFactories.put(beanName, singletonFactory);</span><br><span class="line">            <span class="built_in">this</span>.earlySingletonObjects.remove(beanName);</span><br><span class="line">            <span class="built_in">this</span>.registeredSingletons.add(beanName);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>这里只是添加了一个工厂，通过这个工厂（ObjectFactory）的 getObject 方法可以得到一个对象，而这个对象实际上就是通过 <code>getEarlyBeanReference</code>这个方法创建的</p>
<p>那么什么时候会去调用这个工厂的 getObject 方法呢？这个时候就要到创建 B 的流程了</p>
<p>当 A 完成了实例化并添加进了三级缓存后，就要开始为 A 进行属性注入了</p>
<p>在注入时发现 A 依赖了 B，那么这个时候 Spring 又会去 getBean(b) ，然后反射调用 setter 方法完成属性注入</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/getBean(b).png" alt="getBean(b)"></p>
<blockquote>
<p>因为 B 需要注入 A，所以在创建 B 的时候，又会去调用 getBean(a) </p>
<p>这个时候就又回到之前的流程了，但是不同的是，之前的 getBean 是为了创建 Bean，而此时再调用 getBean 是要从缓存中获取，因为之前 A 在实例化后已经将其放入了三级缓存 </p>
<p><code>singletonFactories</code>中，所以此时 getBean(a) 的流程如下</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/getBean(a).png" alt="getBean(a)"></p>
<blockquote>
<p>可以看出注入到 B 中的 A 是通过 <code>getEarlyBeanReference</code>方法提前暴露出去的一个对象，还不是一个完整的 Bean<br><code>getEarlyBeanReferencen</code>源码如下</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> Object <span class="title function_">getEarlyBeanReference</span><span class="params">(String beanName, RootBeanDefinition mbd, Object bean)</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">exposedObject</span> <span class="operator">=</span> bean;</span><br><span class="line">    <span class="keyword">if</span> (!mbd.isSynthetic() &amp;&amp; hasInstantiationAwareBeanPostProcessors()) &#123;</span><br><span class="line">        <span class="keyword">for</span> (BeanPostProcessor bp : getBeanPostProcessors()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (bp <span class="keyword">instanceof</span> SmartInstantiationAwareBeanPostProcessor) &#123;</span><br><span class="line">                <span class="type">SmartInstantiationAwareBeanPostProcessor</span> <span class="variable">ibp</span> <span class="operator">=</span> (SmartInstantiationAwareBeanPostProcessor) bp;</span><br><span class="line">                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> exposedObject;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>它实际上就是调用了后置处理器的 <code>getEarlyBeanReference</code>，而真正实现了这个方法的后置处理器只有一个，就是通过  <code>@EnableAspectJAutoProxy</code> 注解导入的  <code>AnnotationAwareAspectJAutoProxyCreator</code></p>
<p>也就是说如果在不考虑AOP的情况下，上面的代码等价于：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> Object <span class="title function_">getEarlyBeanReference</span><span class="params">(String beanName, RootBeanDefinition mbd, Object bean)</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">exposedObject</span> <span class="operator">=</span> bean;</span><br><span class="line">    <span class="keyword">return</span> exposedObject;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>也就是说这个工厂啥都没干，直接将实例化阶段创建的对象返回了！</p>
<p>所以说在不考虑AOP的情况下三级缓存有用嘛？讲道理真的没什么用，直接将这个对象放到二级缓存中不是一点问题都没有吗？</p>
<p>无 AOP 的简单循环依赖注入流程如下</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/%E6%97%A0AOP.png" alt="无AOP"></p>
</li>
</ul>
</li>
<li><p>结合了 AOP 的循环依赖</p>
<blockquote>
<p>在普通的循环依赖的情况下，三级缓存没有任何作用</p>
<p>三级缓存实际上跟 Spring 中的 AOP 相关，接下来看一看 <code>getEarlyBeanReference</code>的代码：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> Object <span class="title function_">getEarlyBeanReference</span><span class="params">(String beanName, RootBeanDefinition mbd, Object bean)</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">exposedObject</span> <span class="operator">=</span> bean;</span><br><span class="line">    <span class="keyword">if</span> (!mbd.isSynthetic() &amp;&amp; hasInstantiationAwareBeanPostProcessors()) &#123;</span><br><span class="line">        <span class="keyword">for</span> (BeanPostProcessor bp : getBeanPostProcessors()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (bp <span class="keyword">instanceof</span> SmartInstantiationAwareBeanPostProcessor) &#123;</span><br><span class="line">                <span class="type">SmartInstantiationAwareBeanPostProcessor</span> <span class="variable">ibp</span> <span class="operator">=</span> (SmartInstantiationAwareBeanPostProcessor) bp;</span><br><span class="line">                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> exposedObject;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>如果在开启 AOP 的情况下，那么就是调用到 <code>AnnotationAwareAspectJAutoProxyCreator</code>的 <code>getEarlyBeanReference</code>方法，对应的源码如下：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">getEarlyBeanReference</span><span class="params">(Object bean, String beanName)</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">cacheKey</span> <span class="operator">=</span> getCacheKey(bean.getClass(), beanName);</span><br><span class="line">    <span class="built_in">this</span>.earlyProxyReferences.put(cacheKey, bean);</span><br><span class="line">    <span class="comment">// 如果需要代理，返回一个代理对象，不需要代理，直接返回当前传入的这个bean对象</span></span><br><span class="line">    <span class="keyword">return</span> wrapIfNecessary(bean, beanName, cacheKey);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>对 A 进行了 AOP 代理的话，那么此时 <code>getEarlyBeanReference</code> 将返回一个代理后的对象，而不是实例化阶段创建的对象</p>
<p>这意味着 B 中注入的 A 将是一个代理对象而不是 A 的实例化阶段创建后的对象</p>
<p>有 AOP 的循环依赖注入流程如下</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/AOP.png" alt="AOP"></p>
<ul>
<li><p><strong>在给 B 注入的时候为什么要注入一个代理对象</strong></p>
<blockquote>
<p>当对 A 进行了 AOP 代理时，说明希望从容器中获取到的就是 A 代理后的对象而不是 A 本身，因此把 A 当作依赖进行注入时也要注入它的代理对象</p>
</blockquote>
</li>
<li><p><strong>明明初始化的时候是 A 对象，那么 Spring 是在哪里将代理对象放入到容器中的呢</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/q2.png" alt="q2"></p>
<blockquote>
<p>在完成初始化后，Spring 又调用了一次 <code>getSingleton</code>方法，这次传入的参数又不一样</p>
<p>false 可以理解为禁用三级缓存，前面图中已经提到过了，在为 B 中注入 A 时已经将三级缓存中的工厂取出，并从工厂中获取到了一个对象放入到了二级缓存中，所以这里的这个 <code>getSingleton</code>方法做的时间就是从二级缓存中获取到这个代理后的 A 对象</p>
<p><code>exposedObject == bean</code>可以认为是必定成立的，除非在初始化阶段的后置处理器中替换掉正常流程中的Bean，例如增加一个后置处理器（请不要做这种骚操作，徒增烦恼）</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 在初始化阶段的后置处理器中替换掉正常流程中的Bean</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanPostProcessor</span> &#123;</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> Object <span class="title function_">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException &#123;</span><br><span class="line">		<span class="keyword">if</span> (beanName.equals(<span class="string">&quot;a&quot;</span>)) &#123;</span><br><span class="line">			<span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">A</span>();</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> bean;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>初始化的时候是对 A 对象本身进行初始化，而容器中以及注入到 B 中的都是代理对象，这样不会有问题吗</strong></p>
<blockquote>
<p>不会</p>
<p>因为不管是 cglib 代理还是 jdk 动态代理生成的代理类，内部都持有一个目标类的引用</p>
<p>当调用代理对象的方法时，实际会去调用目标对象的方法，A 完成初始化相当于代理对象自身也完成了初始化</p>
</blockquote>
</li>
<li><p><strong>三级缓存为什么要使用工厂而不是直接使用引用？换而言之，为什么需要这个三级缓存，直接通过二级缓存暴露一个引用不行吗</strong></p>
<blockquote>
<p><strong>这个工厂的目的在于延迟对实例化阶段生成的对象的代理</strong></p>
<p>只有真正发生循环依赖的时候，才去提前生成代理对象，否则只会创建一个工厂并将其放入到三级缓存中，但是不会去通过这个工厂去真正创建对象</p>
<p>思考一种简单的情况，就以单独创建 A 为例，假设 AB 之间现在没有依赖关系，但是 A 被代理了，这个时候当 A 完成实例化后还是会进入下面这段代码</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// A是单例的，mbd.isSingleton()条件满足</span></span><br><span class="line"><span class="comment">// allowCircularReferences：这个变量代表是否允许循环依赖，默认是开启的，条件也满足</span></span><br><span class="line"><span class="comment">// isSingletonCurrentlyInCreation：正在在创建A，也满足</span></span><br><span class="line"><span class="comment">// 所以earlySingletonExposure=true</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">earlySingletonExposure</span> <span class="operator">=</span> (mbd.isSingleton() &amp;&amp; <span class="built_in">this</span>.allowCircularReferences &amp;&amp;</span><br><span class="line">                                  isSingletonCurrentlyInCreation(beanName));</span><br><span class="line"><span class="comment">// 还是会进入到这段代码中</span></span><br><span class="line"><span class="keyword">if</span> (earlySingletonExposure) &#123;</span><br><span class="line">	<span class="comment">// 还是会通过三级缓存提前暴露一个工厂对象</span></span><br><span class="line">    addSingletonFactory(beanName, () -&gt; getEarlyBeanReference(beanName, mbd, bean));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>如上代码，即使没有循环依赖，也会将其添加到三级缓存中，<strong>而且是不得不添加到三级缓存中，因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖</strong></p>
<p>假设直接使用二级缓存的话，那么意味着所有的 Bean 在这一步都要完成 AOP 代理。这样做有必要吗？</p>
<p>不仅没有必要，而且违背了 Spring 在结合 AOP 跟 Bean 的生命周期的设计！</p>
<p>Spring 结合 AOP 跟 Bean 的生命周期本身就是通过 <code>AnnotationAwareAspectJAutoProxyCreator</code>这个后置处理器来完成的，在这个后置处理的 <code>postProcessAfterInitialization</code>方法中对初始化后的 Bean 完成 AOP 代理</p>
<p><strong>如果出现了循环依赖，只有给 Bean 先创建代理，但是没有出现循环依赖的情况下，设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理</strong></p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>总结</p>
<blockquote>
<p>Spring 通过三级缓存解决了循环依赖，其中一级缓存为单例池（singletonObjects），二级缓存为早期曝光对象 <code>earlySingletonObjects</code>，三级缓存为早期曝光对象工厂（singletonFactories）</p>
<p>当 A、B 两个类发生循环引用时，在 A 完成实例化后，就使用实例化后的对象去创建一个对象工厂，并添加到三级缓存中</p>
<p>如果 A 被 AOP 代理，那么通过这个工厂获取到的就是 A 代理后的对象，如果 A 没有被 AOP 代理，那么这个工厂获取到的就是 A 实例化的对象</p>
<p>当 A 进行属性注入时，会去创建 B，同时 B 又依赖了 A，所以创建 B 的同时又会去调用 <code>getBean(A)</code> 来获取需要的依赖，此时的 <code>getBean(A)</code>会从缓存中获取</p>
<p>第一步，先获取到三级缓存中的工厂</p>
<p>第二步，调用对象工厂的 getObject 方法来获取到对应的对象，得到这个对象后将其注入到 B 中</p>
<p>紧接着 B 会走完它的生命周期流程，包括初始化、后置处理器等</p>
<p>当 B 创建完后，会将 B 再注入到 A 中，此时 A 再完成它的整个生命周期</p>
<p>三级缓存流程图如下：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98.png" alt="三级缓存"></p>
<blockquote>
<p>为什么要使用三级缓存呢？二级缓存能解决循环依赖吗</p>
<p><strong>如果要使用二级缓存解决循环依赖，意味着所有 Bean 在实例化后就要完成 AOP 代理，这样违背了 Spring 设计的原则</strong></p>
<p>Spring 在设计之初就是通过 <code>AnnotationAwareAspectJAutoProxyCreator</code>这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理，而不是在实例化后就立马进行 AOP 代理</p>
<p>二级缓存流程图如下：</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/%E4%BA%8C%E7%BA%A7%E7%BC%93%E5%AD%98.png" alt="二级缓存"></p>
</li>
<li><p>循环依赖易错点</p>
<ul>
<li><strong>只有在 setter 方式注入的情况下，循环依赖才能解决（错）</strong></li>
<li><strong>三级缓存的目的是为了提高效率（错）</strong></li>
</ul>
</li>
</ol>
<h3 id="8-Spring-支持的事务管理类型"><a href="#8-Spring-支持的事务管理类型" class="headerlink" title="8.Spring 支持的事务管理类型"></a>8.Spring 支持的事务管理类型</h3><ul>
<li><p>编程式事务管理</p>
<blockquote>
<p>通过编程的方式管理事务，给你带来极大的灵活性，但是难维护</p>
</blockquote>
</li>
<li><p>声明式事务管理</p>
<blockquote>
<p>可以将业务代码和事务管理分离，只需用注解和 XML 配置来管理事务</p>
</blockquote>
</li>
</ul>
<h3 id="9-Spring-事务-7-种传播机制"><a href="#9-Spring-事务-7-种传播机制" class="headerlink" title="9.Spring 事务 7 种传播机制"></a>9.Spring 事务 7 种传播机制</h3><ol>
<li><p>事务传播类型</p>
</li>
<li><p>事务为何失效</p>
</li>
<li><p>AOP 如何支持事务</p>
</li>
<li><p>Spring 怎么保证线程安全</p>
<blockquote>
<p>将 bean 的作用域由 <code>singleton</code> 变更为 <code>prototype</code></p>
</blockquote>
</li>
<li><p>Spring 框架中的单例 Beans 是线程安全的么</p>
<blockquote>
<p>Spring 框架并没有对单例 bean 进行任何多线程的封装处理</p>
<p>关于单例 bean 的线程安全和并发问题需要开发者自行去搞定</p>
<p>但实际上，大部分的 Spring bean 并没有可变的状态（比如 Service 类和 DAO 类），所以在某种程度上说 Spring 的单例 bean 是线程安全的</p>
<p>如果 bean 有多种状态的话（比如 View Model 对象），就需要自行保证线程安全</p>
<p>最浅显的解决办法就是将多态 bean 的作用域由 <code>singleton</code> 变更为 <code>prototype</code></p>
</blockquote>
</li>
</ol>
<h3 id="10-Spring-bean-前置处理器与后置处理器"><a href="#10-Spring-bean-前置处理器与后置处理器" class="headerlink" title="10.Spring bean 前置处理器与后置处理器"></a>10.Spring bean 前置处理器与后置处理器</h3><ol>
<li><p><strong>前置处理器 BeanFactoryPostProcess</strong></p>
<blockquote>
<p>实例化对象之前<br>这个机制允许在实例化相应对象之前对注册到容器中的 BeanDefinition 的存储信息进行修改<br>可以根据这个机制对 Bean 增加其它信息、修改 Bean 定义的某些属性值<br>想自定义前置处理器需要实现 <code>BeanFactoryPostProcess</code>接口<br>当一个容器存在多种前置处理的时候，可以让前置处理器的实现类同时继承 Ordered 接口<br>Spring容器提供了数种现成的前置处理器，常见的如</p>
</blockquote>
<ul>
<li><p>PropertyPlaceholderConfigurer</p>
<blockquote>
<p>允许在 xml 文件中使用占位符<br>将占位符代表的资源单独配置到简单的 Properties 文件中加载</p>
</blockquote>
</li>
<li><p>PropertyOverrideConfigurer</p>
<blockquote>
<p>不同于 PropertyPlaceholderConfigurer，该类用于处理容器中的默认值赋为新值的场景</p>
</blockquote>
</li>
<li><p>CustomEditorConfigurer</p>
<blockquote>
<p>此前的两个前置处理器处理的均是 BeanDefinition<br>通过把 BeanDefinition 的数据修改达到目的<br>CustomEditorConfigurer 没有对 BeanDefinition 做任何变动<br>负责的是将后期会用到的信息注册到容器之中<br>例如将类型转换器注册到 BeanDefinition 中，供 BeanDefinition 将获取到的 String 类型参数转换为需要的类型</p>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>后置处理器 BeanPostProcessor</strong></p>
<blockquote>
<p>实例化对象之后</p>
</blockquote>
<ul>
<li><p><strong>BeanPostProcessor</strong></p>
<blockquote>
<p>bean 工厂的 bean 属性处理容器，就是可以管理 bean 工厂内所有的 beandefinition（未实例化）数据，可以随心所欲的修改属性<br>它有下面两个方法</p>
</blockquote>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>postProcessBeforeInitialization</td>
<td>初始化之前完成一些定制的业务逻辑</td>
</tr>
<tr>
<td>postProcessAfterInitialization</td>
<td>初始化完毕时执行的业务逻辑</td>
</tr>
</tbody></table>
</li>
<li><p><strong>InstantiationAwareBeanPostProcessor</strong></p>
<blockquote>
<p><code>InstantiationAwareBeanPostProcessor</code> 接口继承 <code>BeanPostProcessor</code> 接口，它内部提供了 3 个方法，再加上 <code>BeanPostProcessor</code> 接口内部提供的2个方法，所以实现这个接口需要实现 5 个方法<br><code>InstantiationAwareBeanPostProcessor</code>接口的主要作用在于目标对象的实例化过程中需要处理的事情，包括实例化对象的前后过程以及实例的属性设置</p>
</blockquote>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>postProcessBeforeInstantiation()</td>
<td>在目标对象实例化之前调用，方法的返回值类型是 Object，可以返回任何类型的值<br />由于这个时候目标对象还未被实例化，所以这个返回值可以用来代替原本该生成的目标对象的实例（一般是代理对象）<br />如果该方法的返回值代替原本该生成的目标对象，后续只有 <code>postProcessorAfterIniaialization</code> 方法会调用，其他方法不再调用，否则按照正常流程走</td>
</tr>
<tr>
<td>postProcessAfterInstantiation()</td>
<td>方法在目标对象实例化之后调用，这个时候对象已经被实例化，但是该实例的属性还未被设置，都是 null<br />如果该方法的返回值是 false，会忽略属性值的设置<br />如果返回 true，会按照正常流程设置属性值<br />方法不管 <code>postProcessBeforeInstantiation()</code>返回什么都会执行</td>
</tr>
<tr>
<td>postProcessPropertyValues()</td>
<td>方法对属性值进行修改（这个时候属性值还未被设置，但是可以修改原本该设置进去的属性值）<br />如果 <code>postProcessAfterInstantiation()</code> 返回 false，该方法不会被调用</td>
</tr>
</tbody></table>
</li>
<li><p><strong>SmartInstantiationAwareBeanPostProcessor</strong></p>
<blockquote>
<p>智能实例化 Bean 后置处理器（继承 <code>InstantiationAwareBeanPostProcessor</code>），内部提供了3个方法</p>
</blockquote>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>determineCandidateConstructors()</td>
<td>监测 Bean 的构造器</td>
</tr>
<tr>
<td>predictBeanType()</td>
<td>预测 Bean 的类型</td>
</tr>
<tr>
<td>getEarlyBeanReference()</td>
<td>循环引用的后置处理器，获取提前暴露的 bean 引用<br />主要用于解决循环引用的问题。只有单例对象才会调用此方法</td>
</tr>
</tbody></table>
</li>
<li><p><strong>MergedBeanDefinitionPostProcessor</strong></p>
<blockquote>
<p>合并 bean 信息，或者说合并属性的后置处理器</p>
</blockquote>
<table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>postProcessMergedBeanDefinition()</td>
<td>缓存 bean 注入信息的后置处理器，仅仅是缓存或者说是查询，没有完成注入<br />注入是另外一个后置处理器的作用</td>
</tr>
</tbody></table>
</li>
</ul>
</li>
<li><p>实例化和初始化的区别</p>
<ul>
<li><p>实例化—-是一个创建 Bean 的过程，即调用 Bean 的构造函数，单例的 Bean 放入单例池中</p>
</li>
<li><p>初始化—-是一个赋值的过程，即调用 Bean 的 setter，设置Bean的属性</p>
</li>
</ul>
</li>
</ol>
<h3 id="11-Spring-面向切面编程（AOP）"><a href="#11-Spring-面向切面编程（AOP）" class="headerlink" title="11.Spring 面向切面编程（AOP）"></a>11.Spring 面向切面编程（AOP）</h3><blockquote>
<p>AOP 面向切面的编程</p>
<p>是一种编程技术，允许程序模块化横向切割关注点，或横切典型的责任划分，如日志和事务管理</p>
</blockquote>
<ol>
<li><p>Aspect 切面</p>
<blockquote>
<p>AOP 核心就是切面，它将多个类的通用行为封装成可重用的模块，该模块含有一组 API 提供横切功能</p>
<p>比如，一个日志模块可以被称作日志的 AOP 切面</p>
<p>根据需求的不同，一个应用程序可以有若干切面</p>
<p>在 SpringAOP 中，切面通过带有 <code>@Aspect</code> 注解的类实现</p>
</blockquote>
</li>
<li><p>在 SpringAOP 中，关注点和横切关注的区别是什么</p>
<ul>
<li><p>关注点</p>
<blockquote>
<p>关注点是应用中一个模块的行为，一个关注点可能会被定义成一个我们想实现的一个功能</p>
</blockquote>
</li>
<li><p>横切关注</p>
<blockquote>
<p>横切关注点是一个关注点，此关注点是整个应用都会使用的功能，并影响整个应用</p>
<p>比如日志，安全和数据传输，几乎应用的每个模块都需要的功能</p>
<p>因此这些都属于横切关注点</p>
</blockquote>
</li>
</ul>
</li>
<li><p>连接点</p>
<blockquote>
<p>连接点代表一个应用程序的某个位置，在这个位置我们可以插入一个 AOP 切面</p>
<p>它实际上是个应用程序执行 SpringAOP 的位置</p>
</blockquote>
</li>
<li><p>切点</p>
<blockquote>
<p>切入点是一个或一组连接点，通知将在这些位置执行</p>
<p>可以通过表达式或匹配的方式指明切入点</p>
</blockquote>
</li>
<li><p>通知</p>
<blockquote>
<p>通知是在方法执行前或执行后要做的动作，实际是程序执行时要通过 SpringAOP 框架触发的代码段</p>
<p>Spring 切面可以应用五种类型的通知</p>
</blockquote>
<ul>
<li><code>before</code>：前置通知，在一个方法执行前被调用</li>
<li><code>after</code>：在方法执行之后调用的通知，无论方法执行是否成功</li>
<li><code>after-returning</code>：仅当方法成功完成后执行的通知</li>
<li><code>after-throwing</code>：在方法抛出异常退出时执行的通知</li>
<li><code>around</code>：在方法执行之前和之后调用的通知</li>
</ul>
</li>
<li><p>什么是引入</p>
<blockquote>
<p>引入允许我们在已存在的类中增加新的方法和属性</p>
</blockquote>
</li>
<li><p>什么是目标对象</p>
<blockquote>
<p>被一个或者多个切面所通知的对象</p>
<p>它通常是一个代理对象。也指被通知（advised）对象</p>
</blockquote>
</li>
<li><p>什么是代理</p>
<blockquote>
<p>代理是通知目标对象后创建的对象</p>
<p>从客户端的角度看，代理对象和目标对象是一样的</p>
</blockquote>
</li>
<li><p>有几种不同类型的自动代理</p>
<ul>
<li>BeanNameAutoProxyCreator</li>
<li>DefaultAdvisorAutoProxyCreator</li>
<li>Metadataautoproxying</li>
</ul>
</li>
<li><p>什么是织入</p>
<blockquote>
<p>织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程</p>
<p>织入可以在编译时，加载时，或运行时完成</p>
</blockquote>
</li>
</ol>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm">zcm</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm/2022/10/23/Spring%E9%9D%A2%E7%BB%8F/">https://gitee.com/zcmmmm/zcmmmm/2022/10/23/Spring%E9%9D%A2%E7%BB%8F/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zcmmmm/zcmmmm" target="_blank">无人深空</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Spring/">Spring</a></div><div class="post_share"><div class="social-share" data-image="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/10/26/SpringMVC%E9%9D%A2%E7%BB%8F/"><img class="prev-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">SpringMVC面经</div></div></a></div><div class="next-post pull-right"><a href="/2022/10/22/Linux%E9%9D%A2%E7%BB%8F/"><img class="next-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Linux/Linux.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Linux面经</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/09/23/Spring5%E6%A1%86%E6%9E%B6/" title="Spring5框架"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-09-23</div><div class="title">Spring5框架</div></div></a></div><div><a href="/2022/09/23/SpringMVC/" title="SpringMVC"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Spring/Spring.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-09-23</div><div class="title">SpringMVC</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Spring-%E9%9D%A2%E7%BB%8F"><span class="toc-text">Spring 面经</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-Spring%E3%80%81SpringMVC%E3%80%81SpringBoot%E3%80%81SpringCloud-%E7%9A%84%E5%8C%BA%E5%88%AB%E4%B8%8E%E8%81%94%E7%B3%BB"><span class="toc-text">1.Spring、SpringMVC、SpringBoot、SpringCloud 的区别与联系</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-Spring-%E6%A1%86%E6%9E%B6%E4%B8%AD%E7%9A%84-IOC"><span class="toc-text">2.Spring 框架中的 IOC</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5-DI"><span class="toc-text">3.依赖注入(DI)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-BeanFactory-%E5%92%8C-ApplicationContext-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB"><span class="toc-text">4.BeanFactory 和 ApplicationContext 有什么区别</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-Spring-bean"><span class="toc-text">5.Spring bean</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-Spring-%E5%88%9D%E5%A7%8B%E5%8C%96-bean-%E7%9A%84%E6%B5%81%E7%A8%8B"><span class="toc-text">6.Spring 初始化 bean 的流程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-Spring-%E7%9A%84%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96"><span class="toc-text">7.Spring 的循环依赖</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-Spring-%E6%94%AF%E6%8C%81%E7%9A%84%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86%E7%B1%BB%E5%9E%8B"><span class="toc-text">8.Spring 支持的事务管理类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#9-Spring-%E4%BA%8B%E5%8A%A1-7-%E7%A7%8D%E4%BC%A0%E6%92%AD%E6%9C%BA%E5%88%B6"><span class="toc-text">9.Spring 事务 7 种传播机制</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#10-Spring-bean-%E5%89%8D%E7%BD%AE%E5%A4%84%E7%90%86%E5%99%A8%E4%B8%8E%E5%90%8E%E7%BD%AE%E5%A4%84%E7%90%86%E5%99%A8"><span class="toc-text">10.Spring bean 前置处理器与后置处理器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#11-Spring-%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B%EF%BC%88AOP%EF%BC%89"><span class="toc-text">11.Spring 面向切面编程（AOP）</span></a></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2023 By zcm</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script src="/js/categories.js?v1"></script><script src="/js/navigation.js?v1"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>